import { useEntityManager } from "@sker/core";
import { KsAccount, KsLiveRoom, KsRedPacket } from "@sker/entities";
import dayjs from "dayjs";
import { RedisClientType } from "redis";
import { Between } from "typeorm";
import z from 'zod';
import { publishToTopic } from "../mq";
export const input = z.object({
    name: z.optional(z.string()),
    link: z.optional(z.string()),
    online: z.optional(z.number()),
    openTime: z.string(),
    userId: z.string(),
    count: z.number(),
    type: z.string(),
    uuid: z.string(),
})
type Body = z.infer<typeof input>;
export async function updateRoomInfo(r: RedisClientType, body: any) {
    if (!body.user_id) return;
    if (!body.online) return;
    if (!body.open_time) return;
    const data = await r.get(`kwai.update.rooms.${body.user_id}.${body.type}`)
    try {
        if (data) {
            const json = JSON.parse(data);
            const joins = json.joins
            if (joins && joins.length > 0) {
                return false;
            }
        }
    } catch (e) { }
    await r.set(`kwai.update.rooms.${body.user_id}.${body.type}`, JSON.stringify(body))
    const expireTime = Math.floor((dayjs(body.open_time).toDate().getTime() - dayjs().toDate().getTime()) / 1000)
    await r.expire(`kwai.update.rooms.${body.user_id}.${body.type}`, expireTime)
    return true;
}
/**
 * 保存房间信息到redis
 * @param r 
 * @param body 
 */
export async function addRoomInfo(r: RedisClientType, body: Body) {
    const redPacket = await useEntityManager(async m => {
        let room = await m.findOne(KsLiveRoom, { where: { link: body.link } })
        if (body.userId) {
            const ksAccount = await m.findOne(KsAccount, { where: { user_id: body.userId } })
            if (!ksAccount) {
                const toSave = m.create(KsAccount, {
                    user_id: body.userId,
                    name: body.name || '',
                    ksid: '',
                    has_live: true
                });
                await m.save(KsAccount, toSave);
            } else {
                await m.update(KsAccount, ksAccount.id, { has_live: true })
            }
            // 查找房间
            const start_open_time = dayjs(body.openTime).subtract(10, 'second').toDate();
            const end_open_time = dayjs(body.openTime).add(10, 'second').toDate();
            room = await m.findOne(KsLiveRoom, { where: { user_id: body.userId, open_time: Between(start_open_time, end_open_time) } });
        }
        if (!room) return;
        const redPacket = m.create(KsRedPacket, {
            type: body.type || "",
            count: body.count || 0,
            total: 0,
            open_time: body.openTime,
            password: '',
            text: 'imeepos',
            room_id: room.id
        })
        const { id } = await m.save(KsRedPacket, redPacket)
        return {
            room_id: room.id,
            id: id,
            user_id: body.userId,
            online: body.online,
            link: body.link,
            uuid: body.uuid,
            ...redPacket
        } as KsRedPacket & { user_id: string, online: number };
    })
    if (!redPacket) return;
    const openTime = dayjs(redPacket.open_time).toDate().getTime()
    const nowTime = dayjs().toDate().getTime()
    const dec = openTime - nowTime
    if (dec < 60 * 1000) {
        await r.set(`kwai.update.rooms.${redPacket.user_id}.${redPacket.type}`, JSON.stringify(redPacket))
        const expireTime = Math.floor(dec / 1000)
        await r.expire(`kwai.update.rooms.${redPacket.user_id}.${redPacket.type}`, expireTime)
        return;
    }
    const kwaiInfoRoomsKey = `kwai.info.rooms.${redPacket.user_id}.${redPacket.type}`
    await r.set(kwaiInfoRoomsKey, JSON.stringify(redPacket))
    const expireTime = Math.floor((dayjs(body.openTime).toDate().getTime() - dayjs().toDate().getTime()) / 1000)
    await r.expire(kwaiInfoRoomsKey, expireTime)
    console.log(`[${new Date()}] add a red ${redPacket.user_id} ${expireTime}`)
}
export async function addRoomInfo2(it: any, to: RedisClientType, types: string[] = []) {
    try {
        const reds = it.reds.filter(red => types.includes(red.type))
        const redPackets = reds.map(red => {
            return {
                room_id: red.room_id,
                id: red.id,
                user_id: it.user_id,
                online: it.online,
                link: it.link,
                count: red.count,
                total: red.total,
                type: red.type,
                password: it.password,
                text: 'imeepos',
                open_time: it.open_time,
                update_date: it.update_date,
                create_date: it.create_date,
                uuid: it.uuid,
                owner: it.owner,
                stream_id: it.stream_id,
                ksid: it.ksid
            }
        })
        if (redPackets && redPackets.length > 0) {
            let redPacket = redPackets[0]
            const cyj = redPackets.find(it => it.type === '穿云箭')
            if (cyj) {
                redPacket = cyj;
            }
            const openTime = dayjs(redPacket.open_time).toDate().getTime()
            const nowTime = dayjs().toDate().getTime()
            const dec = openTime - nowTime
            if (dec < 30 * 1000) {
                await to.set(`kwai.update.rooms.${redPacket.user_id}.${redPacket.type}`, JSON.stringify(it))
                const expireTime = Math.floor(dec / 1000)
                await to.expire(`kwai.update.rooms.${redPacket.user_id}.${redPacket.type}`, expireTime)
                return;
            }
            const kwaiInfoRoomsKey = `kwai.info.rooms.${redPacket.user_id}.${redPacket.type}`
            await to.set(kwaiInfoRoomsKey, JSON.stringify(redPacket))
            // 发布到mq
            await publishToTopic(`kwai.share`, kwaiInfoRoomsKey)
            const expireTime = Math.floor((dayjs(redPacket.open_time).toDate().getTime() - dayjs().toDate().getTime()) / 1000)
            await to.expire(kwaiInfoRoomsKey, expireTime)
            console.log(`[${new Date()}] add a red ${redPacket.user_id} ${expireTime}`)
        }
    } catch (e) {
        return;
    }
}
export async function getRoomInfo2(r: RedisClientType, types: string[], uuid?: string, setting?: any) {
    const locker = await acquireLock(r, `kwai.lock.get_room_info2`, 10)
    if (!locker) return;
    const keys = await r.keys(`rooms.*`)
    if (keys.length === 0) {
        await locker()
        return;
    };
    const list = await r.mGet(keys)
    const reds = list.map(it => {
        try {
            return JSON.parse(it)
        } catch (e) {
            return;
        }
    }).filter(it => !!it).map((it: any) => {
        if (it.skip) return;
        if (!it.reds) return
        const red = it.reds.find(red => types.includes(red.type))
        if (!red) return;
        if (!it.online) return;
        const openTime = dayjs(it.open_time).toDate().getTime()
        const nowTime = dayjs().toDate().getTime()
        const min = setting?.minTime || 10;
        const max = setting?.maxTime || 10;
        if ((openTime - nowTime) > getMax(min, max) * 1000) return;
        if ((openTime - nowTime) < getMin(min, max) * 1000) return;
        const moneyV = red.count / it.online
        if (moneyV >= 0.8) return {
            room_id: red.room_id,
            id: red.id,
            user_id: it.user_id,
            online: it.online,
            link: it.link,
            count: red.count,
            total: red.total,
            type: red.type,
            moneyV: moneyV,
            password: it.password,
            text: 'imeepos',
            open_time: it.open_time,
            update_date: it.update_date,
            create_date: it.create_date,
            origin: it
        };
        return;
    }).filter(it => !!it).sort((a, b) => b.moneyV - a.moneyV)
    if (reds && reds.length > 0) {
        const red = reds[0]
        if (red && red.user_id) {
            await r.del(`kwai.info.rooms.${red.user_id}`)
            const data = red.origin;
            data.skip = true;
            await r.set(`rooms.${red.user_id}`, JSON.stringify(data))
            const expireTime = Math.floor((dayjs(red.open_time).toDate().getTime() - dayjs().toDate().getTime()) / 1000)
            await r.expire(`rooms.${red.user_id}`, expireTime)
        }
        await locker();
        return red;
    }
    await locker();
}
export async function getRoomInfo(r: RedisClientType, types: string[], uuid?: string, setting?: any) {
    const locker = await acquireLock(r, `kwai.lock.get_room_info`, 10)
    if (!locker) return;
    const keys = await r.keys(`kwai.info.rooms.*`)
    if (keys.length === 0) {
        await locker()
        return;
    };
    const list = await r.mGet(keys)
    const min = setting?.minTime || 10;
    const max = setting?.maxTime || 10;
    const minMoneyV = setting?.minMoneyV || 0.9;
    const reds = list.map(it => {
        try {
            const data = JSON.parse(it)
            data.online = data.online || 1;
            return data;
        } catch (e) {
            return;
        }
    }).filter(it => !!it).filter((it: any) => {
        if (!types.includes(it.type)) return false;
        const openTime = dayjs(it.open_time).toDate().getTime()
        const nowTime = dayjs().toDate().getTime()
        if ((openTime - nowTime) > getMax(min, max) * 1000) return false;
        if ((openTime - nowTime) < getMin(min, max) * 1000) return false;
        return (it.count / it.online) >= minMoneyV;
    }).sort((a, b) => {
        return b.count / b.online - a.count / a.online
    })
    if (reds && reds.length > 0) {
        const red = reds[0]
        if (red) {
            await r.del(`kwai.info.rooms.${red.user_id}.${red.type}`)
        }
        await locker();
        return red;
    }
    await locker();
}

/**
 * 每30秒一个插座
 * 
 * 01 时间成本
 * 02 收益预估
 * 
 * 单位时间获取币数最高
 * 获取币数  count/online
 * 
 * 最后15s决断
 */
export async function getTimeKey(r: RedisClientType, data: any[]) {
    const start = dayjs().startOf('minute').format('HH:mm:ss')
    const end = dayjs().endOf('minute').format('HH:mm:ss')
    const date = dayjs().format('YYYY-MM-DD')
    await r.hSet(`${date}`, `${start}-${end}`, JSON.stringify(data))
}

export async function removeRoomInfo(r: RedisClientType, red: any) {
    await r.del(`kwai.update.rooms.${red.user_id}.${red.type}`)
}

export async function getUpdateRoomInfo(r: RedisClientType, types: string[], uuid?: string, setting?: any) {
    const locker = await acquireLock(r, `kwai.lock.get_update_room_info.${setting.sn}`, 10)
    if (!locker) return;
    const keys = await r.keys(`kwai.update.rooms.*`)
    if (keys.length === 0) {
        await locker();
        return;
    };
    let min = (setting?.minJoinTime || setting?.minTime || 10);
    let max = (setting?.minOpenTime || setting?.maxTime || 10);
    let min2 = 10;
    if (setting.join_type && setting.join_type === 'share') {
        min += 15;
        min2 += 15;
        max += 15;
    }
    const hourKey = `${dayjs().format('YYYYMMDDHH')}`
    const settingLog = setting.log || {}
    let log = settingLog[hourKey] || ({ count: 0, time: dayjs().toDate().getTime() })
    if (typeof log === 'number') {
        log = {
            count: 0,
            time: dayjs().toDate().getTime()
        }
    }
    const hourTime = (dayjs().toDate().getTime() - log.time) / (1000 * 60);
    let hourTotalV = (log.count || 0) / (hourTime || 1);
    const list = await r.mGet(keys)
    let maxCount = 78 - hourTotalV * 10;
    if (maxCount < 40) {
        maxCount = 40;
    }
    const isWorkHour = !!setting.isWorkHour
    if (!isWorkHour) {
        // 休息时间
        maxCount = 30;
    }
    const redsList = list.map(it => {
        try {
            const data = JSON.parse(it)
            data.online = data.online || 1;
            if (data.type === '穿云箭') {
                data.online = 1;
            }
            return data;
        } catch (e) {
            return;
        }
    }).filter(it => !!it).filter((it: any) => {
        if (!types.includes(it.type)) return false;
        const joins = it.joins || [];
        if (joins.includes(setting?.sn)) {
            return false;
        }
        const openTime = dayjs(it.open_time).toDate().getTime()
        const nowTime = dayjs().toDate().getTime()
        const decTime = openTime - nowTime;
        if (it.type === '穿云箭') return true;
        if (it.type === '口令') {
            if (decTime >= (getMax(min, max)) * 1000 + 5 * 1000) return false;
            if (decTime <= (getMin(min, max)) * 1000 + 5 * 1000) return false;
        } else {
            if (decTime >= (getMax(min, max)) * 1000) return false;
            if (decTime <= (getMin(min, max)) * 1000) return false;
        }
        const y1 = 58 / maxCount;
        const x1 = 10;
        const y2 = 58 / (maxCount - 10);
        const x2 = 15;
        const a = (y2 - y1) / (x2 - x1);
        const b = y2 - a * x2;
        const y = a * Math.floor(decTime / 1000) + b;
        return (it.count / it.online) >= y;
    })
    let red: any = undefined;
    const cyj = redsList.filter(it => it.type === '穿云箭').sort((a, b) => {
        if (b.count === a.count) {
            const aOpenTime = dayjs(a.open_time).toDate().getTime()
            const bOpenTime = dayjs(b.open_time).toDate().getTime()
            return aOpenTime - bOpenTime;
        }
        return b.count - a.count
    })
    if (cyj && cyj.length > 0) {
        red = cyj[0]
    } else {
        const reds = redsList.sort((a, b) => {
            return b.count / a.online - a.count / a.online;
        })
        if (reds && reds.length > 0) {
            red = reds[0]
        }
    }
    if (red) {
        // 找到这个时间点附近的所有包
        const joins = red.joins || [];
        joins.push(setting?.sn || '1');
        red.joins = joins;
        await r.set(`kwai.update.rooms.${red.user_id}.${red.type}`, JSON.stringify(red))
        const expireTime = Math.floor((dayjs(red.open_time).toDate().getTime() - dayjs().toDate().getTime()) / 1000)
        await r.expire(`kwai.update.rooms.${red.user_id}.${red.type}`, expireTime)
        await locker();
        await acquireLock(r, `kwai.lock.get_update_room_info.${setting.sn}`, 6);
        console.log(`${red.count}/${red.online}`);
        return red;
    }
    await locker();
}

const getMin = (a: number, b: number) => a > b ? b : a;
const getMax = (a: number, b: number) => a > b ? a : b;


export async function kwaiSortJoin(r: RedisClientType, userId: string, online: number) {
    const info: Body = await getDataFromRedis(r, `kwai.info.rooms.${userId}`)
    if (info) {
        const count = info.count;
        if (count <= 0 || count <= 0) return;
        const score = online / count;
        await r.zAdd(`kwai.sort.join`, { score, value: userId })
    }
}

export async function kwaiSortFinish(r: RedisClientType, userId: string, count: number, online: number) {
    if (count <= 0) return;
    const score = count / online
    await r.zAdd(`kwai.sort.finish`, { score, value: userId })
}

export async function getKwaiSortJoin(r: RedisClientType) {
    const unlock = await acquireLock(r, 'kwai_sort_join_lock', 30)
    if (unlock) {
        const values = await r.zRange(`kwai.sort.join`, 0, 1)
        if (values && values.length > 0) {
            const userId = values[0];
            if (!userId) {
                await unlock();
                return;
            }
            const info: Body = await getDataFromRedis(r, `kwai.info.rooms.${userId}`)
            if (!info) {
                await r.zRem(`kwai.sort.join`, userId)
                await unlock()
                return;
            }
            if (info) {
                const expireTime = Math.floor((dayjs(info.openTime).toDate().getTime() - dayjs().toDate().getTime()) / 1000)
                if (expireTime < 8) {
                    await r.zRem(`kwai.sort.join`, userId)
                    await unlock()
                    return;
                }
                await r.zRem(`kwai.sort.join`, userId)
                await unlock()
                return info;
            }
        }
        await unlock();
    }
    return;
}

export async function acquireLock(r: RedisClientType, lockKey: string, ttl: number) {
    const isExisits = await r.exists(lockKey)
    if (isExisits) {
        return;
    }
    await r.set(lockKey, `1`)
    await r.expire(lockKey, ttl)
    return () => r.del(lockKey)
}

export async function getKwaiSortAwait(r: RedisClientType) {
    const unlock = await acquireLock(r, 'kwai_sort_await_lock', 60)
    if (unlock) {
        const values = await r.zRange(`kwai.sort.await`, 0, 1)
        if (values && values.length > 0) {
            const userId = values[0];
            // 判断时间是否 在30s内
            const info: Body = await getDataFromRedis(r, `kwai.info.rooms.${userId}`)
            if (!info) {
                await r.zRem(`kwai.sort.await`, userId)
                await unlock()
                return;
            }
            if (info) {
                const expireTime = Math.floor((dayjs(info.openTime).toDate().getTime() - dayjs().toDate().getTime()) / 1000)
                if (expireTime <= 20) {
                    await r.zRem(`kwai.sort.await`, userId)
                    await unlock()
                    return;
                }
                if (expireTime > 30) {
                    await unlock()
                    return;
                }
                await r.zRem(`kwai.sort.await`, userId)
                await unlock()
                return userId;
            }
        }
        await unlock();
    }
}

export async function getDataFromRedis(r: RedisClientType, key: string) {
    const content = await r.get(key)
    if (!content) return;
    try {
        return JSON.parse(content)
    } catch (e) {
        return;
    }
}